package com.fcg.redis.orgtree.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fcg.redis.orgtree.bean.Employee;
import com.fcg.redis.orgtree.bean.OrgBean;
import com.fcg.redis.orgtree.service.OrgService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;

import static com.fcg.redis.rediscommon.util.KeyUtils.*;
import static java.util.Objects.nonNull;
import static java.util.stream.Collectors.*;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;

/**
 * @author FCG
 */
@Slf4j
@Service
public class OrgServiceImpl implements OrgService {

    private static final String NAME_SPACE = "ORG_TREE";
    private static final String DELIMIT = ":";
    private static List<Employee> employees;
    @Resource(name = "redisTemplate")
    private RedisTemplate redisTemplate;
    @Resource(name = "redisTemplate")
    private ListOperations listOperations;
    @Resource(name = "redisTemplate")
    private ValueOperations valueOperations;

    static {
        //集团、公司、部门、员工
        employees = Arrays.asList(
                Employee.of("01", "天路集团", "0101", "天路1号公司",
                        "010101", "人事部", "01010101", "张三"),
                Employee.of("01", "天路集团", "0101", "天路1号公司",
                        "010102", "技术部", "01010201", "李四"),
                Employee.of("01", "天路集团", "0102", "天路2号公司",
                        "010201", "人事部", "01020101", "王五"),
                Employee.of("01", "天路集团", "0102", "天路2号公司",
                        "010202", "技术部", "01020201", "Tom"),
                Employee.of("01", "天路集团", "0103", "天路3号公司",
                        "010301", "人事部", "01030101", "Jerry")
        );
    }

    @PostConstruct
    public void initRedis() {
        saveToRedis();
        log.info("初始化orgTree");
    }

    /**
     * 查询 指定层级下某个父节点的所有子节点
     *
     * @param level    层级
     * @param parentId 父节点id，为空时查询该层级下所有节点
     * @return 指定层级下某个父节点的所有子节点
     */
    @Override
    public List<OrgBean> getSubTree(int level, @Nullable String parentId) {
        List<OrgBean> beanList;
        if (StringUtils.isBlank(parentId) && level != 1) {
            //parentId 为空，模糊匹配查询
            beanList = patternSearch(level);
        } else {
            //parentId 不为空，精确匹配查询
            beanList = exactlySearch(level, parentId);
        }
        return beanList;
    }

    /**
     * 查询 节点信息
     *
     * @param level   层级
     * @param referId 当前节点id
     * @return 当前节点的节点信息
     */
    @Override
    public OrgBean getNode(int level, String referId) {
        String nodeKey = getRedisKey(referId, level);
        Object val = valueOperations.get(nodeKey);
        return JSON.toJavaObject((JSON) val, OrgBean.class);
    }

    @SuppressWarnings("unchecked")
    private List<OrgBean> patternSearch(int level) {
        //SCAN 0 MATCH {NAME_SPACE}:{level}:* COUNT 10000
        String pattern = getRedisKey("*", level);
        log.info("redisKey:{}", pattern);
        List<String> keys = scanKeys(redisTemplate, pattern);
        //过滤掉 string类型的数据
        List<String> listKeys = keys.stream()
                .filter(key -> DataType.LIST.equals(redisTemplate.type(key))).collect(toList());
        if (isNotEmpty(listKeys)) {
            return listKeys.stream().flatMap(key -> {
                List list = listOperations.range(key, 0, -1);
                return deserializeJsonList(list, OrgBean.class).stream();
            }).collect(toList());
        } else {
            return Collections.emptyList();
        }
    }

    @SuppressWarnings("unchecked")
    private List<OrgBean> exactlySearch(int level, String parentId) {
        List<OrgBean> beanList = new ArrayList<>();
        String redisKey = getRedisKey(parentId, level);
        log.info("redisKey:{}", redisKey);
        Boolean hasKey = redisTemplate.hasKey(redisKey);
        if (Boolean.valueOf(true).equals(hasKey)) {
            List jsonList = listOperations.range(redisKey, 0, -1);
            beanList = deserializeJsonList(jsonList, OrgBean.class);
        }
        return beanList;
    }

    private <T> List<T> deserializeJsonList(List jsonList, Class<T> clazz) {
        ArrayList<T> beanList = new ArrayList<>();
        if (isNotEmpty(jsonList)) {
            //反序列化为指定类型的bean
            for (Object o : jsonList) {
                if (nonNull(o)) {
                    try {
                        T bean = JSON.toJavaObject((JSONObject) o, clazz);
                        beanList.add(bean);
                    } catch (Exception ex) {
                        log.info("无法反序列化为指定类型,json:{},clazss:{}", o, clazz);
                    }
                }
            }
        }
        return beanList;
    }

    @SuppressWarnings("unchecked")
    private void saveToRedis() {
        List<OrgBean> tree = getOrgTree();
        List<OrgBean> units = trimChildren(tree);
        String unitKey = getRedisKey(null, 1);
        //删除该命名空间下的所有数据
        delByPattern(redisTemplate, NAME_SPACE + "*");
        //1 层级列表数据
        listOperations.leftPushAll(unitKey, units);
        //1 层级节点数据
        units.forEach(unit -> {
            String key = getRedisKey(unit.getNodeId(), 1);
            valueOperations.set(key, unit);
        });
        tree.forEach(unit -> {
            List<OrgBean> companies = unit.getChildren();
            //2 层级列表数据
            String companyKey = getRedisKey(unit.getNodeId(), 2);
            listOperations.leftPushAll(companyKey, trimChildren(companies));
            companies.forEach(company -> {
                //2 层级节点数据
                String companyRefKey = getRedisKey(company.getNodeId(), 2);
                valueOperations.set(companyRefKey, company);
                List<OrgBean> departs = company.getChildren();
                //3 层级列表数据
                String departKey = getRedisKey(company.getNodeId(), 3);
                listOperations.leftPushAll(departKey, trimChildren(departs));
                departs.forEach(depart -> {
                    //3 层级节点数据
                    String departRefKey = getRedisKey(depart.getNodeId(), 3);
                    valueOperations.set(departRefKey, depart);
                    List<OrgBean> employeeList = depart.getChildren();
                    //4 层级列表数据
                    String empKey = getRedisKey(depart.getNodeId(), 4);
                    listOperations.leftPushAll(empKey, employeeList);
                    //4 层级节点数据
                    employeeList.forEach(emp -> {
                        String empRefKey = getRedisKey(emp.getNodeId(), 4);
                        valueOperations.set(empRefKey, emp);
                    });
                });
            });
        });
    }

    private List<OrgBean> getOrgTree() {
        Map<OrgBean, Map<OrgBean, Map<OrgBean, List<Employee>>>> res = employees.stream()
                .collect(groupingBy(
                        emp -> OrgBean.builder().nodeId(emp.getUnitCode()).nodeName(emp.getUnitName()).build(),
                        mapping(
                                emp -> emp,
                                groupingBy(
                                        emp -> OrgBean.builder().nodeId(emp.getCompanyCode()).nodeName(emp.getCompanyName()).build(),
                                        mapping(
                                                emp -> emp,
                                                groupingBy(emp -> OrgBean.builder().nodeId(emp.getDepartCode()).nodeName(emp.getDepartName()).build())
                                        )
                                )
                        )
                ));
        res.forEach((unit, companies) -> {
            ArrayList<OrgBean> unitChildren = new ArrayList<>(companies.keySet());
            unit.setChildren(unitChildren);
            companies.forEach((company, departments) -> {
                ArrayList<OrgBean> companyChildren = new ArrayList<>(departments.keySet());
                company.setChildren(companyChildren);
                departments.forEach((department, employeeList) -> {
                    List<OrgBean> departChildren = employeeList.stream()
                            .map(emp -> {
                                OrgBean empNode = OrgBean.builder().nodeId(emp.getEmployeeCode()).nodeName(emp.getEmployeeName()).build();
                                empNode.setInfo(emp);
                                return empNode;
                            }).collect(toList());
                    department.setChildren(departChildren);
                });
            });
        });
        return new ArrayList<>(res.keySet());
    }

    public List<OrgBean> trimChildren(List<OrgBean> orgBeans) {
        return orgBeans.stream().map(OrgBean::copyIngnoreChildren).collect(toList());
    }


    /**
     * redisKey: {NAME_SPACE}:{level}:{referId}
     */
    private String getRedisKey(String referId, int level) {
        return concat(DELIMIT, NAME_SPACE, level, referId);
    }


    public static void main(String[] args) {
//        getOrgTree();
        System.out.println("unitCode|unitName|companyCode|");
        employees.forEach(System.out::println);
    }
}
















